import cfg

import calendar
from bisect import bisect_left,bisect_right
from datetime import date,timedelta
import os
import re
from time import strptime,strftime

def offset(date,code,fmt=""):
	code=code.lower().strip()
	d = to_date(date)
	x = 0
	code_split = code.split()
	for c in code_split:
		if re.findall("[+-]?\d+",c):
			x = int(c)
		elif c in available_calendars():
			cal = Calendar(c)
			d = to_date(cal.offset(d.strftime("%Y%m%d"),x))
		elif c in ('day','days'):
			d += timedelta(x)
		elif c in ('week','weeks'):
			d += timedelta(x*7)
		elif c in ('month','months'):
			m = d.year*12+d.month+x
			d = d.replace(year=m/12,month=m%12 if m%12 else 12)
		elif c in ('year','years'):
			d = d.replace(year=d.year+x)
		elif c in ('bom'):
			d = d.replace(day=1)
		elif c in ('boy'):
			d = d.replace(month=1,day=1)
		elif c in ('bow'):
			d = d - timedelta(d.weekday())
		elif c in ('eom'):
			d = (d.replace(day=1)+timedelta(31)).replace(day=1)-timedelta(1)
		elif c in ('eoy'):
			d = d.replace(year=d.year+1,month=1,day=1)-timedelta(1)
		elif c in ('eow'):
			d = d + timedelta(6-d.weekday())
		else:
			raise Exception('unknown code in cal.offset "%s"'%c)
	if re.findall("[+-]?\d+",code_split[-1]):
		d += timedelta(x)
	if not fmt:
		fmt="%Y%m%d"
	return d.strftime(fmt)

### internal #########

to_date_cache={}

def to_date(s):
	if s in to_date_cache:
		return to_date_cache[s]
	else:
		d = date(*strptime(s,"%Y%m%d")[:3])
		to_date_cache[s]=d
		return d

def available_calendars():
	return [c.lower()[:-4] for c in os.listdir(cfg.cal_root_dir) if c.lower().endswith('.ini')]

class Calendar:
	def __init__(self,name):
		self.name=name
		self.cfg={}
		self.load_definition()
	
	def load_definition(self):
		cp = cfg.Parser()
		path = os.path.join(cfg.cal_root_dir,self.name+'.ini')
		cp.read(path)
		for s in cp.sections():
			self.cfg[s] = dict(cp.items(s))
		self.load_definition_stage2()
	
	def load_definition_stage2(self):
		days = set()
		
		s = 'include'
		for k,v in self.cfg.get(s,{}).items():
			if k in ('calendar','calendars'):
				for c in v.split():
					days.update(Calendar(c).days)
			if k in ('year','years'):
				for y in v.split():
					y=int(y)
					for i in range(370):
						d=date(y,1,1)+timedelta(i)
						if d.year==y:
							days.add(d.strftime("%Y%m%d"))
			if k in ('day','days'):
				days.update(set(v.split()))

		s = 'exclude'
		for k,v in self.cfg.get(s,{}).items():
			if k in ('calendar','calendars'):
				for c in v.split():
					days -= set(Calendar(c).days)
			if k in ('dayofweek','daysofweek'):
				daysofweek = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).weekday()+1 not in daysofweek])
			if k in ('dayofmonth','daysofmonth'):
				daysofmonth = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).day not in daysofmonth])
			if k in ('month','months'):
				months = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).month not in months])
			if k in ('year','years'):
				years = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).year not in years])
			if k in ('day','days'):
				days -= set(v.split())

		s = 'select'
		for k,v in self.cfg.get(s,{}).items():
			if k in ('calendar','calendars'):
				for c in v.split():
					days.intersection_update(set(Calendar(c).days))
			if k in ('dayofweek','daysofweek'):
				daysofweek = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).weekday()+1 in daysofweek])
			if k in ('dayofmonth','daysofmonth'):
				# TODO - negative days of month
				daysofmonth = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).day in daysofmonth])
			if k in ('month','months'):
				months = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).month in months])
			if k in ('year','years'):
				years = set([int(x) for x in v.split()])
				days = set([d for d in days if to_date(d).year in years])
			if k in ('day','days'):
				days.intersection_update(set(v.split()))
		self.days = sorted(days)
	
	def offset(self,date,offset):
		try:
			i = self.days.index(date)
		except:
			if offset>0:
				i = max(0,bisect_right(self.days,date)-1)
			elif offset<0:
				i = bisect_right(self.days,date)
			else:
				raise
		try:
			return self.days[i+offset]
		except:
			raise
	
	def daysofmonth(self,date):
		i = bisect_left(self.days,date[:6])
		return [d for d in self.days[i:i+33] if d[4:6]==date[4:6]]

def daycnt_of_month(date):
	d = to_date(date)
	wday,daycnt = calendar.monthrange(d.year,d.month)
	return daycnt

if __name__=="__main__":
	from pprint import pprint
	#pprint(to_date("20110806"))
	c=Calendar("work_noeom")
	pprint(c.daysofmonth('20120205'))
	#print daycnt_of_month("20120202")
	#pprint(c.cfg)
	#pprint(c.days)
	#pprint(len(c.days))
	#print c.offset('20110907',-1)
	#print available_calendars()
	#print offset("20110207","1 holi")
	#c=Calendar("work")
	#print offset("20110907","bom -1 work")


"""
aaa.cnd.ok = -1 MONTH END
bbb.cnd.ok = -3 WORK
"""